package cn.ztuo.bitrade.service;

import cn.ztuo.bitrade.constant.TransactionType;
import cn.ztuo.bitrade.dao.*;
import cn.ztuo.bitrade.dto.CoinDTO;
import cn.ztuo.bitrade.entity.*;
import cn.ztuo.bitrade.service.Base.BaseService;
import cn.ztuo.bitrade.util.BigDecimalUtils;
import cn.ztuo.bitrade.util.PredicateUtils;
import cn.ztuo.bitrade.util.RedisUtil;
import cn.ztuo.bitrade.vo.AccessRecordVO;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.BooleanExpression;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;

/**
 * @author: Xiazt
 * @date: 2020-04-27 16:42
 */
@Slf4j
@Service
public class AccessRecordService extends BaseService {

    @Autowired
    private AccessRecordDao accessRecordDao;

    @Autowired
    private LoanMoneyDao loanMoneyDao;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private MemberWalletDao memberWalletDao;

    @Autowired
    private BorrowMoneyDao borrowMoneyDao;

    @Autowired
    private CoinDao coinDao;

    @Autowired
    private MemberTransactionDao memberTransactionDao;

    @Autowired
    private MemberDao memberDao;

    /**
     * 借入 / 借出
     *
     * @param memberId       // 操作人id
     * @param accessRecord   // 借贷信息
     * @param pledgeCoinUnit // 抵押币种
     * @param moneyId        // 借入 / 借出表id
     */
    @Transactional
    public void access(Long memberId, AccessRecord accessRecord, String pledgeCoinUnit, Long moneyId) {

        if (accessRecord.getMoney().compareTo(BigDecimal.ZERO) <= 0) throw new RuntimeException("数据异常！");

        // 借入/借出币种
        Coin accessCoin = coinDao.findByUnit("USDT");
        // 当天最大时间
        LocalDateTime dayMax = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);

        Member member = memberDao.findOne(memberId);
        accessRecord.setJoinMember(member);
        // 借入操作
        if (accessRecord.getType() == 0) {
            accessRecord.setJoinPledgeCoin(coinDao.findByUnit(pledgeCoinUnit));
            // 借出表加上相应金额
            LoanMoney loanMoney = loanMoneyDao.findById(moneyId);
            accessRecord.setLoanMoney(loanMoney);
            loanMoney.setHasLoanAmount(BigDecimalUtils.add(loanMoney.getHasLoanAmount(), accessRecord.getMoney()));
            if (loanMoney.getLoanAmount().compareTo(loanMoney.getHasLoanAmount()) <= 0) {
                loanMoney.setStatus(2);
            }
            loanMoneyDao.save(loanMoney);
           /* // 借出资产流水
            MemberTransaction transaction = new MemberTransaction();
            transaction.setAmount(accessRecord.getMoney().negate());
            transaction.setMemberId(accessRecord.getMember().getId());
            transaction.setSymbol("USDT");
            transaction.setCreateTime(new Date());
            transaction.setType(TransactionType.LOAN_RECORD);
            memberTransactionDao.save(transaction);*/
            // 计算应还时间
            accessRecord.setRepayTime(dayMax.plusDays(loanMoney.getPeriod()));

            // 扣除抵押金额
            MemberWallet memberWallet = memberWalletDao.findByCoinAndMemberId(accessRecord.getJoinPledgeCoin(), accessRecord.getJoinMember().getId());
            if (accessRecord.getPledgeMoney().compareTo(BigDecimal.ZERO) <= 0) throw new RuntimeException("数据异常！");
            memberWallet.setBalance(BigDecimalUtils.sub(memberWallet.getBalance(), accessRecord.getPledgeMoney()));
            memberWalletDao.save(memberWallet);
            // 抵押币种资产流水
            MemberTransaction pledgeTransaction = new MemberTransaction();
            pledgeTransaction.setMemberId(memberId);
            pledgeTransaction.setSymbol(accessRecord.getJoinPledgeCoin().getUnit());
            pledgeTransaction.setCreateTime(new Date());
            pledgeTransaction.setAmount(accessRecord.getPledgeMoney().negate());
            pledgeTransaction.setType(TransactionType.ACCESS_PLEDGE);
            memberTransactionDao.save(pledgeTransaction);

            // 借入人增加USDT余额
            MemberWallet borrowMemberWallet = memberWalletDao.findByCoinAndMemberId(accessCoin, accessRecord.getJoinMember().getId());
            // 扣除手续费
            if (accessRecord.getServiceCharge().compareTo(BigDecimal.ZERO) <= 0) throw new RuntimeException("数据异常！");
            BigDecimal money = BigDecimalUtils.sub(accessRecord.getMoney(), accessRecord.getServiceCharge());
            borrowMemberWallet.setBalance(BigDecimalUtils.add(borrowMemberWallet.getBalance(), money));
            memberWalletDao.save(borrowMemberWallet);
            // 借入资产流水
            MemberTransaction transaction = new MemberTransaction();
            transaction.setAmount(accessRecord.getMoney());
            transaction.setMemberId(accessRecord.getJoinMember().getId());
            transaction.setSymbol("USDT");
            transaction.setCreateTime(new Date());
            transaction.setType(TransactionType.LOAN_RECORD);
            memberTransactionDao.save(transaction);

            // 手续费资产流水
            MemberTransaction serviceChangeTransaction = new MemberTransaction();
            serviceChangeTransaction.setMemberId(memberId);
            serviceChangeTransaction.setSymbol("USDT");
            serviceChangeTransaction.setCreateTime(new Date());
            serviceChangeTransaction.setAmount(accessRecord.getServiceCharge().negate());
            serviceChangeTransaction.setType(TransactionType.ACCESS_SERVICE_CHARGE);
            memberTransactionDao.save(serviceChangeTransaction);

            accessRecord.setStatus(2);
        } else {
            // 借出操作
            BorrowMoney borrowMoney = borrowMoneyDao.findById(moneyId);
            accessRecord.setBorrowMoney(borrowMoney);
            // 借入表加上相应金额
            borrowMoney.setHasBorrowAmount(BigDecimalUtils.add(borrowMoney.getHasBorrowAmount(), accessRecord.getMoney()));
            if (borrowMoney.getBorrowAmount().compareTo(borrowMoney.getHasBorrowAmount()) <= 0) {
                borrowMoney.setStatus(2);
            }
            borrowMoneyDao.save(borrowMoney);

            // 计算应还时间
            accessRecord.setRepayTime(dayMax.plusDays(borrowMoney.getPeriod()));

            // 扣除借出人USDT钱包余额
            MemberWallet loanWallet = memberWalletDao.findByCoinAndMemberId(accessCoin, accessRecord.getJoinMember().getId());
            loanWallet.setBalance(BigDecimalUtils.sub(loanWallet.getBalance(), accessRecord.getMoney()));
            memberWalletDao.save(loanWallet);
            // 借出资产流水
            MemberTransaction loanTransaction = new MemberTransaction();
            loanTransaction.setAmount(accessRecord.getMoney().negate());
            loanTransaction.setMemberId(accessRecord.getJoinMember().getId());
            loanTransaction.setSymbol("USDT");
            loanTransaction.setCreateTime(new Date());
            loanTransaction.setType(TransactionType.LOAN_RECORD);
            memberTransactionDao.save(loanTransaction);

            // 增加借入人USDT钱包余额
            MemberWallet borrowWallet = memberWalletDao.findByCoinAndMemberId(accessCoin, borrowMoney.getJoinMember().getId());
            borrowWallet.setBalance(BigDecimalUtils.add(borrowWallet.getBalance(), accessRecord.getMoney()));
            memberWalletDao.save(borrowWallet);
            // 借入资产流水
            MemberTransaction transaction = new MemberTransaction();
            transaction.setAmount(accessRecord.getMoney());
            transaction.setMemberId(accessRecord.getJoinMember().getId());
            transaction.setSymbol("USDT");
            transaction.setCreateTime(new Date());
            transaction.setType(TransactionType.LOAN_RECORD);
            memberTransactionDao.save(transaction);

            // 借款记录状态
            accessRecord.setStatus(0);
        }
        AccessRecord result = accessRecordDao.save(accessRecord);
        // 计算时间差存入redis
        long repayTimeDifference = ChronoUnit.SECONDS.between(LocalDateTime.now(), accessRecord.getRepayTime());
        redisUtil.set("repayTime:difference:" + result.getId(), result.getId(), repayTimeDifference);
        // 存入redis一条数据，到期前一天过期
        redisUtil.set("repayTime:beforeDay:" + result.getId(), result.getId(), repayTimeDifference - 86400);

    }

    /**
     * 我的 借入 / 借出单
     *
     * @param accessRecordVO
     * @return
     */
    public Page<AccessRecord> getMyAccessRecord(AccessRecordVO accessRecordVO) {
        /*ArrayList<BooleanExpression> booleanExpressions = new ArrayList<>();
        // 借入记录
        if (accessRecordVO.getType() == 0) {
            booleanExpressions.add((QAccessRecord.accessRecord.type.eq(0).and(QAccessRecord.accessRecord.member.id.eq(accessRecordVO.getMemberId())))
                    .or(QAccessRecord.accessRecord.type.eq(1).and(QAccessRecord.accessRecord.borrowMoney.member.id.eq(accessRecordVO.getMemberId()))));
        } else {
            // 借出记录
            booleanExpressions.add((QAccessRecord.accessRecord.type.eq(1).and(QAccessRecord.accessRecord.member.id.eq(accessRecordVO.getMemberId())))
                    .or(QAccessRecord.accessRecord.type.eq(0).and(QAccessRecord.accessRecord.loanMoney.member.id.eq(accessRecordVO.getMemberId()))));
        }
        if (accessRecordVO.getStatus() != null) {
            booleanExpressions.add(QAccessRecord.accessRecord.status.eq(accessRecordVO.getStatus()));
        }
        Predicate predicate = PredicateUtils.getPredicate(booleanExpressions);*/
        // 排序条件
        Sort sort = new Sort(Sort.Direction.DESC, "createTime");
        Pageable pageable = new PageRequest(accessRecordVO.getPageNum() - 1, accessRecordVO.getPageSize(), sort);
//        return accessRecordDao.findAll(predicate, pageable);

        Page<AccessRecord> accessRecords = accessRecordDao.findAll(new Specification<AccessRecord>() {
            @Override
            public javax.persistence.criteria.Predicate toPredicate(Root<AccessRecord> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                javax.persistence.criteria.Predicate p1 = null;
                javax.persistence.criteria.Predicate p2 = null;
                javax.persistence.criteria.Predicate p3 = null;
                javax.persistence.criteria.Predicate p4 = null;
                Join<AccessRecord, Member> accessRecordMemberJoin = root.join("joinMember", JoinType.LEFT);
                if (accessRecordVO.getType() == 0) {
                    p1 = cb.equal(root.get("type"), 0);
                    p2 = cb.equal(accessRecordMemberJoin.get("id"), accessRecordVO.getMemberId());

                    p3 = cb.equal(root.get("type"), 1);
                    Join<AccessRecord, BorrowMoney> borrowMoneyJoin = root.join("borrowMoney", JoinType.LEFT);
                    Join<BorrowMoney, Member> memberJoin = borrowMoneyJoin.join("joinMember", JoinType.LEFT);
                    p4 = cb.equal(memberJoin.get("id"), accessRecordVO.getMemberId());
                } else {
                    p1 = cb.equal(root.get("type"), 1);
                    p2 = cb.equal(accessRecordMemberJoin.get("id"), accessRecordVO.getMemberId());

                    p3 = cb.equal(root.get("type"), 0);
                    Join<AccessRecord, LoanMoney> loanMoneyJoin = root.join("loanMoney", JoinType.LEFT);
                    Join<LoanMoney, Member> memberJoin = loanMoneyJoin.join("joinMember", JoinType.LEFT);
                    p4 = cb.equal(memberJoin.get("id"), accessRecordVO.getMemberId());
                }
                javax.persistence.criteria.Predicate and1 = cb.and(p1, p2);
                javax.persistence.criteria.Predicate and2 = cb.and(p3, p4);
                return cb.or(and1, and2);
            }
        }, pageable);

        accessRecords.forEach(accessRecord -> {
            CoinDTO coinDTO = new CoinDTO(null, null);
            if (accessRecord.getType() == 0) {
                coinDTO.setName(accessRecord.getJoinPledgeCoin().getName());
                coinDTO.setUnit(accessRecord.getJoinPledgeCoin().getUnit());
                accessRecord.setPledgeCoin(coinDTO);
            } else {
                coinDTO.setName(accessRecord.getBorrowMoney().getJoinPledgeCoin().getName());
                coinDTO.setUnit(accessRecord.getBorrowMoney().getJoinPledgeCoin().getUnit());
                accessRecord.getBorrowMoney().setPledgeCoin(coinDTO);
            }
        });
        return accessRecords;
    }

    /**
     * 后台 借入 / 借出单
     *
     * @param accessRecordVO
     * @return
     */
    public Page<AccessRecord> getAccessRecord(AccessRecordVO accessRecordVO) {
        ArrayList<BooleanExpression> booleanExpressions = new ArrayList<>();
        if (accessRecordVO.getType() != null) {
            booleanExpressions.add(QAccessRecord.accessRecord.type.eq(accessRecordVO.getType()));
        }
        if (accessRecordVO.getUsername() != null) {
            booleanExpressions.add(QAccessRecord.accessRecord.joinMember.username.eq(accessRecordVO.getUsername()));
        }
        if (accessRecordVO.getPhoneNumber() != null) {
            booleanExpressions.add(QAccessRecord.accessRecord.joinMember.mobilePhone.eq(accessRecordVO.getPhoneNumber()));
        }
        if (accessRecordVO.getStatus() != null) {
            booleanExpressions.add(QAccessRecord.accessRecord.status.eq(accessRecordVO.getStatus()));
        }
        Predicate predicate = PredicateUtils.getPredicate(booleanExpressions);
        // 排序条件
        Sort sort = new Sort(Sort.Direction.DESC, "createTime");
        Pageable pageable = new PageRequest(accessRecordVO.getPageNum() - 1, accessRecordVO.getPageSize(), sort);
        return accessRecordDao.findAll(predicate, pageable);
    }

    /**
     * 还款
     *
     * @param accessId
     */
    @Transactional
    public void repayment(Long accessId) {
        // 借入 / 借出币种
        Coin accessCoin = coinDao.findByUnit("USDT");
        AccessRecord accessRecord = accessRecordDao.findOne(accessId);
        LoanMoney loanMoney = null;
        BorrowMoney borrowMoney = null;
        MemberWallet borrowWallet = null;
        MemberWallet loanWallet = null;

        Member borrowMember = new Member();
        Member loanMember = new Member();

        BigDecimal interest = BigDecimal.ZERO;


        if (accessRecord.getType() == 0) {
            borrowMember = accessRecord.getJoinMember();
            loanMember = accessRecord.getLoanMoney().getJoinMember();
            loanMoney = accessRecord.getLoanMoney();
            // 借入人USDT钱包
            borrowWallet = memberWalletDao.findByCoinAndMemberId(accessCoin, accessRecord.getJoinMember().getId());
            // 借出人USDT钱包
            loanWallet = memberWalletDao.findByCoinAndMemberId(accessCoin, loanMoney.getJoinMember().getId());
            // 修改借出表状态
            loanMoney.setStatus(1);
            loanMoneyDao.save(loanMoney);

            // 退回借入人质押金
            if (accessRecord.getPledgeMoney().compareTo(BigDecimal.ZERO) <= 0) throw new RuntimeException("数据异常！");
            Coin pledgeCoin = coinDao.findByUnit(accessRecord.getJoinPledgeCoin().getUnit());
            MemberWallet pledgeMemberWallet = memberWalletDao.findByCoinAndMemberId(pledgeCoin, accessRecord.getJoinMember().getId());
            pledgeMemberWallet.setBalance(BigDecimalUtils.add(pledgeMemberWallet.getBalance(), accessRecord.getPledgeMoney()));
            memberWalletDao.save(pledgeMemberWallet);

            // 质押金退回记录
            MemberTransaction borrowTransaction = new MemberTransaction();
            borrowTransaction.setMemberId(accessRecord.getJoinMember().getId());
            borrowTransaction.setSymbol(pledgeCoin.getUnit());
            borrowTransaction.setCreateTime(new Date());
            borrowTransaction.setAmount(accessRecord.getPledgeMoney());
            borrowTransaction.setType(TransactionType.CANCELLATIONS_PLEDGE);
            memberTransactionDao.save(borrowTransaction);
            // 利息
            interest = accessRecord.getInterest();
        } else {
            borrowMember = accessRecord.getBorrowMoney().getJoinMember();
            loanMember = accessRecord.getJoinMember();

            borrowMoney = accessRecord.getBorrowMoney();
            // 借入人USDT钱包
            borrowWallet = memberWalletDao.findByCoinAndMemberId(accessCoin, borrowMoney.getJoinMember().getId());
            // 借出人USDT钱包
            loanWallet = memberWalletDao.findByCoinAndMemberId(accessCoin, accessRecord.getJoinMember().getId());
            // 修改借入表状态
            borrowMoney.setStatus(1);
            borrowMoneyDao.save(borrowMoney);

            // 退回借入人质押金
            if (borrowMoney.getPledgeMoney().compareTo(BigDecimal.ZERO) <= 0) throw new RuntimeException("数据异常！");
            Coin pledgeCoin = coinDao.findByUnit(borrowMoney.getJoinPledgeCoin().getUnit());
            MemberWallet pledgeMemberWallet = memberWalletDao.findByCoinAndMemberId(pledgeCoin, borrowMoney.getJoinMember().getId());
            pledgeMemberWallet.setBalance(BigDecimalUtils.add(pledgeMemberWallet.getBalance(), borrowMoney.getPledgeMoney()));
            memberWalletDao.save(pledgeMemberWallet);

            // 质押金退回记录
            MemberTransaction borrowTransaction = new MemberTransaction();
            borrowTransaction.setMemberId(borrowMoney.getJoinMember().getId());
            borrowTransaction.setSymbol(pledgeCoin.getUnit());
            borrowTransaction.setCreateTime(new Date());
            borrowTransaction.setAmount(accessRecord.getBorrowMoney().getPledgeMoney());
            borrowTransaction.setType(TransactionType.CANCELLATIONS_PLEDGE);
            memberTransactionDao.save(borrowTransaction);

            // 利息
            interest = borrowMoney.getInterestDue();
        }
        // 扣除借入人USDT钱包余额
        if (accessRecord.getMoney().compareTo(BigDecimal.ZERO) <= 0) throw new RuntimeException("数据异常！");
        if (interest.compareTo(BigDecimal.ZERO) <= 0) throw new RuntimeException("数据异常！");
        BigDecimal money = BigDecimalUtils.add(accessRecord.getMoney(), interest);
        borrowWallet.setBalance(BigDecimalUtils.sub(borrowWallet.getBalance(), money));
        memberWalletDao.save(borrowWallet);
        //封装交易记录
        MemberTransaction borrowTransaction = new MemberTransaction();
        borrowTransaction.setMemberId(borrowMember.getId());
        borrowTransaction.setSymbol("USDT");
        borrowTransaction.setCreateTime(new Date());
        borrowTransaction.setAmount(money.negate());
        // 借贷流水
        borrowTransaction.setType(TransactionType.REPAYMENT_LOAN);
        memberTransactionDao.save(borrowTransaction);

        // 增加借出人USDT钱包余额
        loanWallet.setBalance(BigDecimalUtils.add(loanWallet.getBalance(), money));
        memberWalletDao.save(loanWallet);
        //封装交易记录
        MemberTransaction loanTransaction = new MemberTransaction();
        loanTransaction.setMemberId(loanMember.getId());
        loanTransaction.setSymbol("USDT");
        loanTransaction.setCreateTime(new Date());
        loanTransaction.setAmount(money);
        // 借贷流水
        loanTransaction.setType(TransactionType.REPAYMENT_LOAN);
        memberTransactionDao.save(loanTransaction);

        // 修改借款记录状态
        accessRecord.setStatus(3);
        accessRecordDao.save(accessRecord);

        // 删除redis记录
        redisUtil.delete("repayTimeDifference:" + accessRecord.getId());
    }

    /**
     * 补仓
     *
     * @param memberId
     * @param accessRecordId
     * @param money
     */
    @Transactional
    public void variationCall(Long memberId, Long accessRecordId, BigDecimal money) {
        if (money.compareTo(BigDecimal.ZERO) <= 0) throw new RuntimeException("数据异常！");

        AccessRecord accessRecord = accessRecordDao.findById(accessRecordId);
        MemberWallet memberWallet = memberWalletDao.findByCoinAndMemberId(accessRecord.getJoinPledgeCoin(), memberId);
        memberWallet.setBalance(BigDecimalUtils.sub(memberWallet.getBalance(), money));
        memberWalletDao.save(memberWallet);
        // 封装交易记录
        MemberTransaction variationCallTransaction = new MemberTransaction();
        variationCallTransaction.setMemberId(memberId);
        variationCallTransaction.setCreateTime(new Date());
        variationCallTransaction.setAmount(money);
        variationCallTransaction.setType(TransactionType.VARIATION_CALL);
        if (accessRecord.getType() == 0) {
            accessRecord.setPledgeMoney(BigDecimalUtils.add(accessRecord.getPledgeMoney(), money));
            variationCallTransaction.setSymbol(accessRecord.getJoinPledgeCoin().getUnit());
        } else {
            BorrowMoney borrowMoney = accessRecord.getBorrowMoney();
            borrowMoney.setPledgeMoney(BigDecimalUtils.add(borrowMoney.getPledgeMoney(), money));
            borrowMoneyDao.save(borrowMoney);
            variationCallTransaction.setSymbol(borrowMoney.getJoinPledgeCoin().getUnit());
        }
        if (accessRecord.getReminder() == 1) {
            accessRecord.setReminder(0);
        }
        accessRecordDao.save(accessRecord);
        memberTransactionDao.save(variationCallTransaction);
    }

    public AccessRecord findById(Long id) {
        return accessRecordDao.findById(id);
    }

}
